﻿using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Collections.Generic;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder;
using Microsoft.Practices.EnterpriseLibrary.Common.Instrumentation;
using EntLibContrib.ExceptionHandling.Configuration;

using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
using System.Data.SqlClient;

namespace EntLibContrib.ExceptionHandling
{
    /// <summary>
    /// Exception Handler that can map a SqlException with nested SqlError objects to custom .NET exceptions specified in configuration.
    /// </summary>
    [ConfigurationElementType(typeof(SqlExceptionWrapHandlerData))]
    public class SqlExceptionWrapHandler : IExceptionHandler
    {
        private ErrorCodeNotConfiguredBehavior errorCodeNotConfiguredBehavior;
        private string defaultExceptionMessage;
        private Type defaultExceptionType;
        private NamedElementCollection<ErrorCodeEntryData> errorCodeEntries;

        /// <summary>
        /// <para>Initializes a new instance of the <see cref="SqlExceptionWrapHandler"/>.</para>
        /// </summary>
        /// <param name="configuration">The configuration object used to set the runtime values</param>
        public SqlExceptionWrapHandler(SqlExceptionWrapHandlerData configuration)
        {
            this.errorCodeNotConfiguredBehavior = configuration.ErrorCodeNotConfiguredBehavior;
            this.defaultExceptionMessage = configuration.DefaultExceptionMessage;
            this.defaultExceptionType = configuration.DefaultWrapExceptionType;
            this.errorCodeEntries = configuration.ErrorCodes;
        }

  

        #region IExceptionHandler Members

        /// <summary>
        /// Handles the exception.
        /// </summary>
        /// <param name="exception">The original exception.</param>        
        /// <param name="handlingInstanceId">The unique identifier attached to the handling chain for this handling instance.</param>
        /// <returns>Modified exception to pass to the next exceptionHandlerData in the chain.</returns>
        public Exception HandleException(Exception exception, Guid handlingInstanceId)
        {
            SqlException sqlException = exception as SqlException;
            if (sqlException == null)
            {
                throw new ExceptionHandlingException("The SqlExceptionWrapHandler can only operate on SqlException types.");
            }

            ErrorCodeEntryData matchingEntry = null;
            // Iterate through all of the SQL Errors 
            foreach (SqlError error in sqlException.Errors)
            {
                // Iterate through the configured error codes to see if any match
                foreach (ErrorCodeEntryData entry in errorCodeEntries)
                {
                    if (entry.ErrorCode == error.Number)
                    {
                        matchingEntry = entry;
                        break;
                    }
                }

                // If we've found a matching error code, break out
                if (matchingEntry != null)
                {
                    break;
                }
            }
           

            if (matchingEntry == null)
            {
                // no mapping exists
                if (errorCodeNotConfiguredBehavior == ErrorCodeNotConfiguredBehavior.WrapWithDefaultException)
                {
                    return WrapException(exception, defaultExceptionType, defaultExceptionMessage);
                }
                else
                {
                    return exception;
                }
            }
            else
            {
                // Mapping was found, so return the exception specified in configuration
                return WrapException(exception, matchingEntry.WrapExceptionType, matchingEntry.ExceptionMessage);
            }


        }

        #endregion

        private Exception WrapException(Exception originalException, Type wrapExceptionType, string wrapExceptionMessage)
        {
            object[] extraParameters = new object[2];
            extraParameters[0] = wrapExceptionMessage;
            extraParameters[1] = originalException;
            return (Exception)Activator.CreateInstance(wrapExceptionType, extraParameters);
        }
    }
}
